package mac10.connection;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Connection-Class, creates the server connection and
 * handles sending and receiving of Documents.
 */
public class ServerConnection {

	private InetSocketAddress socketaddress;

	private Socket socket;

	private InputStream inputstream;

	private OutputStream outputstream;

	private DocumentBuilderFactory documentbuilderfactory;

	private TransformerFactory transformerfactory;

	private Log log = null;

	/**
	 * Constructor, creates a new ClimaServerConnection
	 */
	public ServerConnection(String host, int port, Log log) throws IOException {
		socket = new Socket();
		socketaddress = new InetSocketAddress(host, port);
		this.log = log;

		try {
			socket.connect(socketaddress);
			inputstream = socket.getInputStream();
			outputstream = socket.getOutputStream();
			log.info(
					new StringBuffer("Initialising server connection done, host=")
					.append(host)
					.append(" port=")
					.append(port));
		} catch (IOException e) {
			log.error(
					new StringBuffer("Error during initialization! host=")
					.append(host)
					.append(" port=")
					.append(port));
			throw e;
		}
		documentbuilderfactory = DocumentBuilderFactory.newInstance();
		transformerfactory = TransformerFactory.newInstance();
	}

	/**
	 * This method retrieves a raw byte-stream and stores it into a byte-array.
	 * 
	 * @return - a byte-array containing the received data
	 * @throws IOException
	 * @throws RuntimeException
	 */
	private byte[] receivePacket() throws IOException, RuntimeException {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		int read = inputstream.read();
		while (read != 0) {
			if (read == -1) {
				throw new RuntimeException("Socket Closed");
			}
			buffer.write(read);
			read = inputstream.read();
		}
		return buffer.toByteArray();
	}

	/**
	 * This method is used to receive a new document from the server.
	 * 
	 * @return - the received document
	 * @throws RuntimeException 
	 * @throws IOException 
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws RuntimeException
	 * @throws ParserConfigurationException 
	 * @throws SAXException 
	 */
	public Document receiveDocument() throws Exception {		
		try {
			byte[] raw = receivePacket();
			Document doc = documentbuilderfactory.newDocumentBuilder().parse(new ByteArrayInputStream(raw));
			return doc;
		} catch (IOException e) {
			log.error("unable to read packets");
			throw e;
		} catch (RuntimeException e) {
			log.error("runtime exception occured!");
			throw e;
		}  catch (SAXException e) {
			log.error("SAX exception occured!");
			throw e;
		} catch (ParserConfigurationException e) {
			log.error("parser configuration exception occured");
			throw e;
		}
	}

	/**
	 * This method is used to send a document instantly to the server.
	 * 
	 * @param doc - the document to send
	 */
	public void sendDocument(Document doc) throws Exception {
		try {
			transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(outputstream));
			outputstream.write(0);
		} catch (TransformerConfigurationException e) {
			log.error("unable to configure transformer");
			throw e;
		} catch (TransformerException e) {
			log.error("unable to transform document");
			throw e;
		} catch (IOException e) {
			log.error("an io-exception occured!");
			throw e;
		}
	}
}
